package org.novelhibernate.support.logging;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import org.apache.log4j.Logger;
import org.novelhibernate.exception.CustomHandlerException;
import org.novelhibernate.exception.NoHandlerException;
import org.novelhibernate.exception.NovelhibernateException;
import org.novelhibernate.support.logging.annotation.ExceptionHandleMethod;
import org.novelhibernate.support.logging.annotation.ExceptionRegistry;


public abstract class LogFactorySupport implements GenericLogFactory {

	private static final Logger log = Logger.getLogger(LogFactorySupport.class);

	// This suppose to be a read-only cache after initilization
	private Map<String, DefaultExceptionHandler> handlers = new HashMap<String, DefaultExceptionHandler>();
	
	public void doGeneralLoggingTasks(Method method, String logMess,
			Object... args) {
		
		log.debug("Log for method: " + method.getName());
		
		Logger.getLogger(method.getDeclaringClass()).info(
				"Method: " + method.getName() + " | Message: " + logMess);

	}

	public void doExceptionLoggingTasks(Method method, String logMess,
			Throwable t, Object... args) {

		log.debug("Log for method: " + method.getName());
		
		Logger.getLogger(method.getDeclaringClass()).error(
				"Method: " + method.getName() + " | Message: " + logMess, t);
	}

	public void finallizationCallback(FinallizedObject finalObject,
			boolean isHandleException) throws Throwable {

		log.debug("Start finallization call back for "
				+ ((isHandleException) ? "catch" : "finally"));
		// args[handler.bindedObjectArrayIndex()]).length +
		// "************\n");

		Object[] targets = (isHandleException) ? finalObject
				.getExceptionObjects() : finalObject.getFinalObjects();

		String[][] methods = (isHandleException) ? finalObject
				.getExceptionMethods() : finalObject.getFinalMethods();

		if (null != targets)
			for (int i = 0; i < targets.length; i++) {

				for (int j = 0; j < methods[i].length; j++)
					targets[i].getClass().getDeclaredMethod(methods[i][j]).invoke(
							targets[i]);

			}

	}

	public Object exceptionCallback(Object target, Method method, Throwable t,
			Object[] args, Class<?> instance) throws Throwable {

		log.debug("Start exception call back for " + method.getName());

		ExceptionRegistry registry = method
				.getAnnotation(ExceptionRegistry.class);
		Class<?>[] exceptions = registry.handleException();
		
		Class<?> targetClass = (instance == null)? target.getClass() : instance;
		
		int[] ids = registry.handlerMehotdID();

		if (ids.length != exceptions.length)
			throw new NoHandlerException(target.getClass().getName() + "."
					+ method.getName());

		ExceptionHandleMethod handlerMethod = null;
		
		Method executor = null;
		// find the exception
		for (int i = 0; i < exceptions.length; i++) {
			
			
			if (exceptions[i].isInstance(t) || exceptions[i].isInstance(t.getCause())) {
			
				for (Method handler : targetClass.getDeclaredMethods()) {
				
					handlerMethod = handler
							.getAnnotation(ExceptionHandleMethod.class);

					// find the handler method
					if (null != handlerMethod
							&& handlerMethod.methodID() == ids[i]) {

						// only the method of original instance can be called
						executor = target.getClass().getDeclaredMethod(handler.getName(), handler.getParameterTypes());
						break;

					}
				}

				// if a exception found then should be able to find the
				// hanlder as well
				// otherwise throw exception
				if (executor != null)
					break;
				else
					throw new NoHandlerException(exceptions[i], target
							.getClass().getName()
							+ "." + method.getName());

			}

		}
		
		Object retValue = null;
		if (executor != null) {

			if (executor.getParameterTypes().length != 1 || !executor.getParameterTypes()[0].isInstance(args))
				throw new CustomHandlerException("The parameter of custom handler should be Object[] only");  
			else if (executor.getReturnType() == Void.TYPE)
				throw new CustomHandlerException("The return type of custom handler should not be void");
			
				
			retValue = executor.invoke(target, new Object[]{args});

		
		// if no custom exception handler found then
		// carry on throw the exception
	    } else
			throw new NovelhibernateException();
 
			
			
		return retValue;
	}
	
	
	public synchronized void setDefaultExceptionHandler(String id, DefaultExceptionHandler handler)
	           throws NovelhibernateException{	
		
		if(handlers.containsKey(id))
			 throw new NovelhibernateException("The default hanlder " + id + " has been registered");
		
		handlers.put(id, handler);
	}

	public DefaultExceptionHandler getDefaultExceptionHandler(String id){	
		return handlers.get(id);
	}
}
